package com.ssm.basis.service.util;

import com.ssm.basis.service.exception.CustomException;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.zip.*;

public abstract class ZipUtil {
    /**
     * 获取 zip 压缩包中的指定文件
     *
     * @param file     zip 文件
     * @param filePath 要解压 zip 中文件的路径
     */
    public static InputStream readFileInZip(File file, String filePath) {
        try {
            // ZipInputStream zipFile = new ZipInputStream(new ClassPathResource("templates/word.docx").getInputStream());
            ZipFile zipFile = new ZipFile(file);
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            ZipEntry zipEntry;
            InputStream zipEntryInputStream = null;
            while (entries.hasMoreElements()) {
                zipEntry = entries.nextElement();
                if (filePath.equalsIgnoreCase(zipEntry.getName())) {
                    zipEntryInputStream = zipFile.getInputStream(zipEntry);
                    break;
                }
            }
            return zipEntryInputStream;
        } catch (IOException e) {
            throw new CustomException(e);
        }
    }

    /**
     * 用新文件替换 zip 中的指定文件
     *
     * @param file            zip 文件
     * @param replaceFilePath 要替换的文件路径
     * @param replaceFile     要替换进去的新文件
     * @return 新 zip 压缩包文件流
     */
    public static FastByteArrayOutputStream replaceFileInZip(File file, String replaceFilePath, File replaceFile) {
        try {
            FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream();
            ZipOutputStream zos = new ZipOutputStream(fbaos); // 新 zip 文件

            ZipFile zipFile = new ZipFile(file); // 原 zip 文件
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            ZipEntry zipEntry;
            InputStream zipEntryInputStream;
            while (entries.hasMoreElements()) {
                zipEntry = entries.nextElement();

                zos.putNextEntry(new ZipEntry(zipEntry.getName()));
                zipEntryInputStream = zipFile.getInputStream(zipEntry);
                if (replaceFilePath.equalsIgnoreCase(zipEntry.getName())) {
                    zipEntryInputStream.close();
                    zipEntryInputStream = new FileInputStream(replaceFile);
                }
                // zipEntryInputStream.transferTo(zos);
                StreamUtils.copy(zipEntryInputStream, zos);
                zipEntryInputStream.close();
                zos.closeEntry();
            }

            zos.close();
            return fbaos;
        } catch (IOException e) {
            throw new CustomException(e);
        }
    }

    public static void zipCompressionToFile(String zipPath, String... paths) {
        try {
            FileCopyUtils.copy(zipCompression(paths).toByteArray(), Paths.get(zipPath).toFile());
        } catch (IOException e) {
            throw new CustomException(e);
        }
    }

    /**
     * 压缩文件
     */
    public static FastByteArrayOutputStream zipCompression(String... paths) {
        try (FastByteArrayOutputStream fbaos = new FastByteArrayOutputStream();
             // 对目标文件做 CRC32 校验，确保压缩后的 zip 包含 CRC32 值
             CheckedOutputStream cos = new CheckedOutputStream(fbaos, new CRC32());
             ZipOutputStream zos = new ZipOutputStream(cos)) {
            // 设置压缩级别 0-9，0 表示不压缩，1 表示压缩速度最快，9 表示压缩后文件最小；默认为 6，速率和空间上得到平衡。
            zos.setLevel(9);

            List<Path> ps = Arrays.stream(paths).map(Paths::get).collect(Collectors.toList());
            for (Path path : ps) {
                Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
                    @Override // 访问一个文件
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        String filePath = file.toString().replace(path.getParent().toString(), "");
                        zos.putNextEntry(new ZipEntry(filePath));
                        Files.copy(file, zos);
                        zos.closeEntry();
                        return FileVisitResult.CONTINUE;
                    }

                    @Override // 访问一个目录
                    public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                        String dirPath = dir.toString().replace(path.getParent().toString(), "") + File.separator;
                        // TODO 无法打包空文件夹
                        zos.putNextEntry(new ZipEntry(dirPath));
                        zos.closeEntry();
                        return FileVisitResult.CONTINUE;
                    }
                });
            }
            return fbaos;
        } catch (IOException e) {
            throw new CustomException(e);
        }
    }
}